package com.hong.mvc.servlet;

import com.hong.mvc.annotation.Controller;
import com.hong.mvc.annotation.RequestMapping;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * Created by wanghong
 * Date 2019-08-06 15:30
 * Description:
 * 中央控制器
 */
public class DispatcherServlet extends HttpServlet {

    private static final long serialVersionID = 1L;

    // 读取配置
    private Properties properties = new Properties();

    // 类的全路径名集合
    private List<String> classNames = new ArrayList<>();

    // ioc
    private Map<String, Object> ioc = new HashMap<>();

    // 保存url何controller的映射关系
    private Map<String, Method> handlerMapping = new HashMap<>();

    private Map<String, Object> controllerMap = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        // 2.初始化所有相关联的类，扫描用户指定的包下的所有类
        doScanner(properties.getProperty("scanPackage"));

        // 3.获取扫描到的类，通过反射机制实例化，并且放到IOC容器中(k-v beanName-bean)，beanName默认是首字母小写
        doInstance();

        // 4.初始化handlerMapping(将url和method对应上)
        initHandlerMapping();

        // 5.实现注入，主要针对service注入到controller
        // doIoc();
    }

    private void doLoadConfig(String location){
        if (location.startsWith("classpath:")){
            location = location.replace("classpath:","");
        }else if (location.contains("/")){
            int lastSplitIndex = location.lastIndexOf("/");
            location = location.substring(lastSplitIndex + 1);
        }

        // 把web.xml中的contextConfigLocation对应value值文件加载到流中
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (resourceAsStream != null){
                try {
                    resourceAsStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doScanner(String packageName){
        String scan = "/" + packageName.replaceAll("\\.", "/");
        URL url = this.getClass().getClassLoader().getResource(scan);
        File dir = new File(url.getFile());
        for (File file:dir.listFiles()){
            if (file.isDirectory()){
                doScanner(packageName + "." + file.getName());
            }else {
                String className = packageName + "." + file.getName().replace(".class","");
                classNames.add(className);
                System.out.println("Spring容器扫描到的类:" + className);
            }
        }

    }

    private void doInstance(){
        if (classNames.isEmpty()){
            return;
        }

        for (String className:classNames){
            try {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(Controller.class)){
                    Controller controller = clazz.getAnnotation(Controller.class);
                    String key = controller.value();
                    if (null != key && !"".equals(key)){
                        ioc.put(key,clazz.newInstance());
                    }else {
                        // 只拿字节码上标有Controller.class对象的信息
                        ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void initHandlerMapping() {
        if (ioc.isEmpty()){
            return;
        }

        for (Map.Entry<String,Object> entry:ioc.entrySet()){
            Class<? extends Object> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(Controller.class)){
                continue;
            }
            String baseUrl = "";
            if (clazz.isAnnotationPresent(RequestMapping.class)){
                RequestMapping annotation = clazz.getAnnotation(RequestMapping.class);
                baseUrl = annotation.value();
            }
            Method[] methods = clazz.getMethods();
            for (Method method:methods){
                if (!method.isAnnotationPresent(RequestMapping.class)){
                    continue;
                }
                RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                String url = annotation.value();
                url = (baseUrl + "/" + url).replaceAll("/+","/");
                handlerMapping.put(url,method);

                Object tmpvalue = null;
                String ctlName = toLowerFirstWord(clazz.getSimpleName());
                if (ioc.containsKey(ctlName)){
                    tmpvalue = ioc.get(ctlName);
                }else {
                    try {
                        tmpvalue = clazz.newInstance();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
                controllerMap.put(url,tmpvalue);
                System.out.println(url + "," + method);
            }
        }
    }

    private String toLowerFirstWord(String str) {
        char [] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            resp.getWriter().write("500! Server Exception");
        }
    }

    /**
     * 请求分发
     *
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        if (handlerMapping.isEmpty()) {
            return;
        }

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        // 拼接URL并把多个/替换成一个
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        if (!handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 not found");
            return;
        }

        Method method = handlerMapping.get(url);
        // 获取请求方法的参数列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 获取请求的参数
        Map<String, String[]> parameterMap = req.getParameterMap();
        // 保存参数值
        Object[] paramValues = new Object[parameterTypes.length];
        // 方法的参数列表
        for (int i = 0; i < parameterTypes.length; i++) {
            // 根据参数名称，做一些特殊处理
            String requestParam = parameterTypes[i].getSimpleName();
            if ("HttpServletRequest".equals(requestParam)){
                // 类型明确，可以强转
                paramValues[i] = req;
                continue;
            }
            if ("HttpServletResponse".equals(requestParam)){
                paramValues[i] = resp;
                continue;
            }
            if ("String".equals(requestParam)){
                for (Map.Entry<String,String[]> param:parameterMap.entrySet()){
                    String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]","");
                    paramValues[i] = value;
                }
            }

        }

        method.invoke(controllerMap.get(url),paramValues);

    }
}
